home *** CD-ROM | disk | FTP | other *** search
/ Just Call Me Internet / Just Call Me Internet.iso / prog / atari / c / snz128s / src / snews2.c < prev    next >
C/C++ Source or Header  |  1994-06-02  |  38KB  |  1,644 lines

  1. /*
  2.     SNEWS 2.0
  3.  
  4.     snews - a simple threaded news reader
  5.  
  6.  
  7.     Copyright (C) 1991    John McCombs, Christchurch, NEW ZEALAND
  8.                         john@ahuriri.gen.nz
  9.                         PO Box 2708, Christchurch, NEW ZEALAND
  10.  
  11.     This program is free software; you can redistribute it and/or modify
  12.     it under the terms of the GNU General Public License, version 1, as
  13.     published by the Free Software Foundation.
  14.  
  15.     This program is distributed in the hope that it will be useful,
  16.     but WITHOUT ANY WARRANTY; without even the implied warranty of
  17.     MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
  18.     GNU General Public License for more details.
  19.  
  20.     See the file COPYING, which contains a copy of the GNU General
  21.     Public License.
  22.  
  23.     Atari version ported by Graham Judd - gjudd@siward.demon.co.uk
  24.  
  25.  */
  26.  
  27. /*---------------------------- Source Control ------------------------------*/
  28.  
  29. /*
  30.  * $Id: snews2.c,v 1.2 1994/02/05 18:52:38 gbj Exp user $
  31.  */
  32.  
  33.  
  34. /****************************************************************************
  35. *   20 May 92   1.2     GT  ka9q mods.                                      *
  36. *   22 May 92   1.3     GT  Set time zone.                                  *
  37. *   05 Jun 92   1.4     GT  Nikki Locke's SPACE command.                    *
  38. *   06 Jun 92   1.5     GT  Invalidate freed pointers.                      *
  39. *   09 Jun 92   1.6     GT  Right and left cursor keys.                     *
  40. *   12 Jun 92   1.7     NJL Add v_init call & change to use scr_lines.      *
  41. *                           Add 'q' as alternative to ESC throughout.       *
  42. *                           Fix memory leak when Escaping from read_thread. *
  43. *                           Ensure strtok() accepts tab as well as space.   *
  44. *   17 Jul 92   1.8     GT  C++ compilation.                                *
  45. *                           Heap debugging.                                 *
  46. *                           Swap out when executing child process.          *
  47. *   16 Aug 92   1.9    MSM  Snews 1.9                                       *
  48. *                           Lock history etc. during use                    *
  49. *   31 May 93   1.10   MSM  Snews 2.0                                       *
  50. *   18 Jun 93   1.11   MSM  Heap debugging removed (using Bounds Check)     *
  51. *   10 Jul 93   1.12   MSM  New commands added, help updated                *
  52. *    7 Aug 93   1.13   MSM  Change command functionality to be TINish       *
  53. *                      MSM  Add expert mode                                 *
  54. *                      MSM  Add confirm exit mode                           *
  55. *   26 Aug 93   1.13   MSM  Colour Support clean up, Expert toggle, bugs    *
  56. *    3 Oct 92   1.14   MSM  Case sensitive y/n questions made insensitive   *
  57. *                           old style tab action added, new config options  *
  58. *                           132 col / 43 line support corrected             *
  59. *                           article/thread save function corrected.         *
  60. *   24 Nov 93   1.15   MSM  Changed to Delta version control                *
  61. *                           Session setting text changes                    *
  62. *   25 Nov 93   1.16   MSM  Correct read list processing                    *
  63. *    5 Feb 94    AT2    GBJ  Atari port.                        *
  64. *    2 Apr 94   1.17   MSM  Add print thread support                        *
  65. *                abort/replace/append choice for thread save     *
  66. *                15.89 style added to smart match                *
  67. *                Save full newsgroup                    *
  68. *                Suspend support                    *
  69. *****************************************************************************/
  70.  
  71.  
  72. #include <time.h>
  73. #include <io.h>
  74. #include <fcntl.h>
  75. #include "defs.h"
  76. #include "snews.h"
  77. #include "screen.h"
  78. #ifdef ATARI
  79. #    include <sys/types.h>
  80. #    include "st.h"
  81. #    include "fileops.h"
  82. #else
  83. #    include "exec.h"
  84. #endif
  85. #include "locking.h"
  86.  
  87. #ifndef __TURBOC__
  88. #ifndef ATARI
  89. #include <graph.h>
  90. #include <dos.h>
  91. #define BLACK 0
  92. #define LIGHTGRAY 7
  93. unsigned long farcoreleft(void);
  94. #endif
  95. #endif
  96.  
  97.  
  98. extern int             xfile;
  99.  
  100. extern INFO            my_stuff;
  101. #ifdef ATARI
  102. extern unsigned long _STACK;
  103. #else
  104. extern unsigned        _stklen;
  105. #endif
  106. extern char    search_text[];
  107. extern char     save_name[];
  108. extern  char    search_group[];
  109.  
  110. /*------------------------- find which thread to read ----------------------*/
  111. int             select_thread(ACTIVE * gp, ARTICLE * head)
  112. {
  113.  
  114.     /*
  115.      * Present the list of threads, and allow him to move up and down with
  116.      * the arrow and PgUp and PgDn keys. 
  117.      */
  118.  
  119.     ARTICLE        *top;                  /* thread at the top of the page */
  120.     ARTICLE        *this_thread;          /* current thread */
  121.     ARTICLE        *th;
  122.     ART_ID         *art;
  123.     int             exit_code;              /* why we are exiting the loop */
  124.     char            sub_tmp[80];
  125.  
  126.     int             ch, i, idx, hit, a_ct, ch2;
  127.  
  128.     this_thread = head;
  129.     top = head;
  130.     exit_code = 0;
  131.  
  132.     show_threads(gp, &top, this_thread, TRUE, head);
  133.  
  134.     while (exit_code == 0) {
  135.  
  136.         ch = getch();
  137.         switch (ch) {
  138.  
  139.           case 0:
  140.           case 0xE0:
  141.             ch = getch();
  142.  
  143.             switch (ch) {
  144.  
  145.               case Fn1:
  146.                 show_help(HELP_THREAD);
  147.                 show_threads(gp, &top, this_thread, TRUE, head);
  148.                 break;
  149.  
  150.               case Fn2:
  151.                 show_values();
  152.                 show_threads(gp, &top, this_thread, TRUE, head);
  153.                 break;
  154.  
  155.               case Fn3:
  156.                 change_values();
  157.                 show_threads(gp, &top, this_thread, TRUE, head);
  158.                 break;
  159.  
  160.               case UP_ARR:
  161.                 if (this_thread->last != NULL)
  162.                     this_thread = this_thread->last;
  163.                 break;
  164.  
  165.               case DN_ARR:
  166.                 if (this_thread->next != NULL)
  167.                     this_thread = this_thread->next;
  168.                 break;
  169.  
  170.               case PGUP:
  171.                 for (i = this_thread->index - top->index; i < PAGE_LENGTH; i++) {
  172.                     if (this_thread->last == NULL)
  173.                         break;
  174.                     this_thread = this_thread->last;
  175.                 }
  176.                 break;
  177.  
  178.               case PGDN:
  179.                 for (i = this_thread->index - top->index; i < PAGE_LENGTH; i++) {
  180.                     if (this_thread->next == NULL)
  181.                         break;
  182.                     this_thread = this_thread->next;
  183.                 }
  184.                 break;
  185.  
  186.               case HOME:
  187.                 top = this_thread = head;
  188.                 show_threads(gp, &top, this_thread, TRUE, head);
  189.                 break;
  190.  
  191.               case END:
  192.                 top = this_thread = head;
  193.                 while (this_thread->next != NULL)
  194.                     this_thread = this_thread->next;
  195.                 break;
  196.  
  197.               case RT_ARR:
  198.                 read_thread(gp, this_thread, this_thread->art_num, 1);
  199.                 show_threads(gp, &top, this_thread, TRUE, head);
  200.                 break;
  201.  
  202.               case LT_ARR:
  203.                 exit_code = EX_QUIT;
  204.                 break;
  205.             }
  206.             break;
  207.  
  208.             case 'k':
  209.             if (this_thread->last != NULL)
  210.                 this_thread = this_thread->last;
  211.             break;
  212.  
  213.           case 'j':
  214.             if (this_thread->next != NULL)
  215.                 this_thread = this_thread->next;
  216.             break;
  217.  
  218.           case   2:
  219.           case  21:
  220.           case 'b':
  221.             for (i = 0; i < PAGE_LENGTH; i++) {
  222.                 if (this_thread->last == NULL)
  223.                     break;
  224.                 this_thread = this_thread->last;
  225.             }
  226.             break;
  227.  
  228.           case   4:
  229.           case   6:
  230.           case ' ':
  231.             for (i = 0; i < PAGE_LENGTH; i++) {
  232.                 if (this_thread->next == NULL)
  233.                     break;
  234.                 this_thread = this_thread->next;
  235.             }
  236.             break;
  237.  
  238.           case '!':
  239.             textbackground(headb);
  240.             textcolor(headf);
  241.             clrscr();
  242. #ifdef ATARI
  243.             printf("Sorry, function not supported. \n");
  244.             printf("Press a key to return to Snews.\n\n");
  245.             getch();
  246. #else
  247.             printf("Type <EXIT> to return to Snews.\n\n");
  248.             system("");
  249. #endif
  250.             textbackground(textb);
  251.             textcolor(textf);
  252.             clrscr();
  253.             show_threads(gp, &top, this_thread, TRUE, head);
  254.             break;
  255.  
  256.           case 's':
  257.             xfile = FALSE;
  258.             save_thread_to_disk(gp, this_thread);
  259.             getch();
  260.             show_threads(gp, &top, this_thread, TRUE, head);
  261.             break;
  262.  
  263.           case 'S':
  264.             xfile = TRUE;
  265.             save_thread_to_disk(gp, this_thread);
  266.             xfile = FALSE;
  267.             getch();
  268.             show_threads(gp, &top, this_thread, TRUE, head);
  269.             break;
  270.  
  271.           case 'M':
  272.             mail_to_someone(NULL);
  273.             show_threads(gp, &top, this_thread, TRUE, head);
  274.             break;
  275.  
  276.           case 'v':
  277. #ifdef ATARI
  278.             sprintf(sub_tmp, "Demon Internet Simple News v%02d.%02d.%02d LC5 compiled %s", rmj, rmm, rup, __DATE__);
  279. #else
  280. #ifdef __TURBOC__
  281.             sprintf(sub_tmp, "Demon Internet Simple News v%02d.%02d.%02d BC++ compiled %s", rmj, rmm, rup, __DATE__);
  282. #else
  283.             sprintf(sub_tmp, "Demon Internet Simple News v%02d.%02d.%02d MSVC compiled %s", rmj, rmm, rup, __DATE__);
  284. #endif
  285. #endif
  286.             message(sub_tmp);
  287.             getch();
  288.             show_threads(gp, &top, this_thread, TRUE, head);
  289.             break;
  290.  
  291.           case 'w':
  292.             strcpy(sub_tmp, "");
  293.             if (gp->suspend == TRUE) {
  294.                 message("This group is suspended, post regardless ? (y/n) ");
  295.                 ch2 = getch();
  296.                 ch2 = tolower(ch2);
  297.                 message("");
  298.             }
  299.             else
  300.                 ch2 = 'y';
  301.             if (ch2 == 'y') {
  302.                 post(NULL, gp->group, sub_tmp);
  303.                 show_threads(gp, &top, this_thread, TRUE, head);
  304.             }
  305.             break;
  306.  
  307.           case 'o':
  308.               print_thread(gp, this_thread);
  309.               message("-- Done --");
  310.               break;
  311.  
  312.           case 'h':
  313.           case 'H':
  314.             show_help(HELP_THREAD);
  315.             show_threads(gp, &top, this_thread, TRUE, head);
  316.             break;
  317.  
  318.           case TAB:
  319.           case 'N':
  320.             if (my_stuff.tab_action == TRUE) {
  321.  
  322.                 /*
  323.                  * Go to the next unread article.    Work through each
  324.                  * thread, looking at each article to see if it's been
  325.                  * read
  326.                  */
  327.  
  328.                 /* for each thread */
  329.                 th = this_thread;
  330.                 hit = FALSE;
  331.                 while (th != NULL) {
  332.  
  333.                     art = th->art_num;
  334.                     a_ct = 0;
  335.  
  336.                     /* for each article */
  337.                     while (art != NULL) {
  338.                         idx = (int) (art->id - gp->lo_num - 1);
  339.                         a_ct++;
  340.                         if (*((gp->read_list) + idx) == FALSE) {
  341.                             hit = TRUE;
  342.                             break;
  343.                         }
  344.                         art = art->next_art;
  345.                     }
  346.                     if (hit)
  347.                         break;
  348.                     th = th->next;
  349.                 }
  350.  
  351.                 if (hit) {
  352.                     this_thread = th;
  353.                     read_thread(gp, this_thread, art, a_ct);
  354.                     show_threads(gp, &top, this_thread, TRUE, head);
  355.                 }
  356.                 else {
  357.                    exit_code = EX_NEXT;
  358.                 }
  359.                 break;
  360.             }
  361.             else { /* old style tab action */
  362.                 /*
  363.                  *    Go to the next unread article.    Work through each
  364.                  *    thread, looking at each article to see if it's been
  365.                  *    read
  366.                  */
  367.  
  368.                 /* for each thread */
  369.                 th = this_thread;
  370.                 hit = FALSE;
  371.                 while (th != NULL) {
  372.  
  373.                     art = th->art_num;
  374.                     a_ct = 0;
  375.  
  376.                     /* for each article */
  377.                     while (art != NULL) {
  378.                         idx = (int)(art->id - gp->lo_num - 1);
  379.                         a_ct++;
  380.                         if ( *((gp->read_list)+idx) == FALSE) {
  381.                             hit = TRUE;
  382.                             break;
  383.                         }
  384.                         art = art->next_art;
  385.                     }
  386.                     if (hit) break;
  387.                     th = th->next;
  388.                 }
  389.  
  390.                 if (hit) {
  391.                     this_thread = th;
  392.                     read_thread(gp, this_thread, art, a_ct);
  393.                     show_threads(gp, &top, this_thread, TRUE, head);
  394.                 } else {
  395.                     message("-- No more articles to read --");
  396.                 }
  397.                 break;
  398.             }
  399.  
  400.           case 'P':
  401.  
  402.             /*
  403.              * Go to the previous unread article.    Work through each
  404.              * thread, looking at each article to see if it's been
  405.              * read
  406.              */
  407.  
  408.             /* for each thread */
  409.             th = this_thread->last;
  410.             hit = FALSE;
  411.             while (th != NULL) {
  412.  
  413.                 art = th->art_num;
  414.                 a_ct = 0;
  415.  
  416.                 /* for each article */
  417.                 while (art != NULL) {
  418.                     idx = (int) (art->id - gp->lo_num - 1);
  419.                     a_ct++;
  420.                     if (*((gp->read_list) + idx) == FALSE) {
  421.                         hit = TRUE;
  422.                         break;
  423.                     }
  424.                     art = art->next_art;
  425.                 }
  426.                 if (hit)
  427.                     break;
  428.                 th = th->last;
  429.             }
  430.  
  431.             if (hit) {
  432.                 this_thread = th;
  433.                 read_thread(gp, this_thread, art, a_ct);
  434.                 show_threads(gp, &top, this_thread, TRUE, head);
  435.             }
  436.             else {
  437.                message("-- No previous articles unread --");
  438.             }
  439.             break;
  440.  
  441.           case 'X':
  442.             for(th=head;th!=this_thread;th=th->next)
  443.                 mark_thread_as_read(gp, th, TRUE);
  444.             show_threads(gp, &top, this_thread, TRUE, head);
  445.             break;
  446.  
  447.           case 'K':
  448.             mark_thread_as_read(gp, this_thread, FALSE);
  449.             th = this_thread;
  450.             hit = FALSE;
  451.             while (th != NULL) {
  452.  
  453.                 art = th->art_num;
  454.                 a_ct = 0;
  455.  
  456.                 /* for each article */
  457.                 while (art != NULL) {
  458.                     idx = (int) (art->id - gp->lo_num - 1);
  459.                     a_ct++;
  460.                     if (*((gp->read_list) + idx) == FALSE) {
  461.                         hit = TRUE;
  462.                         break;
  463.                     }
  464.                     art = art->next_art;
  465.                 }
  466.                 if (hit)
  467.                     break;
  468.                 th = th->next;
  469.             }
  470.             if (th != NULL)
  471.                  this_thread = th;
  472.             show_threads(gp, &top, this_thread, TRUE, head);
  473.             if (th == NULL)
  474.                 message("-- No more articles to read --");
  475.             break;
  476.  
  477.           case 'C':
  478.             if (mark_group_as_read(gp, TRUE) == TRUE)
  479.                 exit_code = EX_NEXT;
  480.             else
  481.                 lmessage("");
  482.             break;
  483.  
  484.           case 'c':
  485.             if (mark_group_as_read(gp, TRUE) == TRUE)
  486.                 show_threads(gp, &top, this_thread, TRUE, head);
  487.             else
  488.                 lmessage("");
  489.             break;
  490.  
  491.           case 'z':
  492.           case 'Z':
  493.             mark_thread_as_unread(gp, this_thread);
  494.             show_threads(gp, &top, this_thread, TRUE, head);
  495.             break;
  496.  
  497.           case ENTER:
  498.             read_thread(gp, this_thread, this_thread->art_num, 1);
  499.             show_threads(gp, &top, this_thread, TRUE, head);
  500.             break;
  501.  
  502.           case BACKSP:
  503.             art = this_thread->art_num;
  504.             a_ct =1;
  505.             while (art->next_art != NULL) {
  506.                 a_ct++;
  507.                 art = art->next_art;
  508.             }
  509.             read_thread(gp, this_thread, art, a_ct);
  510.             show_threads(gp, &top, this_thread, TRUE, head);
  511.             break;
  512.  
  513.           case '+':
  514.           case '=':
  515.           case '/':
  516.             this_thread = search_thread(gp, this_thread, ch=='/', TRUE);
  517.             show_threads(gp, &top, this_thread, TRUE, head);
  518.             break;
  519.  
  520.           case '-':
  521.           case '?':
  522.             this_thread = search_thread(gp, this_thread, ch=='?', FALSE);
  523.             show_threads(gp, &top, this_thread, TRUE, head);
  524.             break;
  525.  
  526.           case 'B':
  527.             bug_report();
  528.             show_threads(gp, &top, this_thread, TRUE, head);
  529.             break;
  530.  
  531.           case ESCAPE:
  532.           case 'q':
  533.             exit_code = EX_QUIT;
  534.             break;
  535.         };
  536.         if (exit_code == 0)
  537.             show_threads(gp, &top, this_thread, FALSE, head);
  538.     }
  539. return exit_code;
  540.  
  541. }
  542.  
  543.  
  544. /*------------------------ mark thread as read ------------------------------*/
  545. int                mark_thread_as_read(ACTIVE *gp, ARTICLE *this_thread, int q)
  546. {
  547.  
  548.     ART_ID         *id;
  549.     int             idx, ch;
  550.  
  551.     if ((q == 0) && (my_stuff.expert != TRUE)) {
  552.         message("Mark all articles in thread as read (y/n) [y] ? ");
  553.         do
  554.             {
  555.             ch = getch();
  556.             ch = tolower(ch);
  557.         }
  558.         while ((ch != 'y') && (ch != 'n') && (ch != ESCAPE) && (ch != '\r'));
  559.     }
  560.     else
  561.         ch = 'y';
  562.  
  563.     if ((ch == 'y') || (ch == '\r')) {
  564.         id = this_thread->art_num;
  565.         while (id != NULL) {
  566.             /* mark this article as read */
  567.             idx = (int) ((id->id) - gp->lo_num - 1);
  568.             *((gp->read_list) + idx) = (char) TRUE;
  569.  
  570.             id = id->next_art;
  571.         }
  572.         message("");
  573.         return TRUE;
  574.     }
  575.     message("");
  576.     return FALSE;
  577. }
  578.  
  579. /*------------------------ mark thread as unread ------------------------------*/
  580. void            mark_thread_as_unread(ACTIVE *gp, ARTICLE *this_thread)
  581. {
  582.  
  583.     ART_ID         *id;
  584.     int             idx, ch;
  585.  
  586.     if (my_stuff.expert != TRUE) {
  587.         message("Mark all articles in thread as unread (y/n) [y] ? ");
  588.         do
  589.             {
  590.             ch = getch();
  591.             ch = tolower(ch);
  592.         }
  593.         while ((ch != 'y') && (ch != 'n') && (ch != ESCAPE) && (ch != '\r'));
  594.     }
  595.     else
  596.         ch = 'y';
  597.  
  598.     if ((ch == 'y') || (ch == '\r')) {
  599.         id = this_thread->art_num;
  600.         while (id != NULL) {
  601.             /* mark this article as read */
  602.             idx = (int) ((id->id) - gp->lo_num - 1);
  603.             *((gp->read_list) + idx) = (char) FALSE;
  604.  
  605.             id = id->next_art;
  606.         }
  607.     }
  608.     message("");
  609. }
  610.  
  611.  
  612. /*------------------------ save a newsgroup ------------------------------*/
  613. void            save_group_to_disk(ACTIVE * gp)
  614. {
  615.     char fnx[80];
  616.     FILE *tmp, *tmp_file;
  617.     char *fn, ch;
  618.     char buf[512], prompt[80];
  619.  
  620.     sprintf(prompt, "Enter filename [%s] ", save_name);
  621.     lmessage(prompt);
  622.     gets(fnx);
  623.     if (strlen(fnx) > 0)
  624.         strcpy(save_name, fnx);
  625.     else
  626.         strcpy(fnx, save_name);
  627.  
  628.     tmp = NULL;
  629.  
  630.     if (access(fnx, 0) == 0) {
  631.         message("File exists - append/replace/exit (a/r/e)? ");
  632.         do {
  633.             ch = getch();
  634.             ch = tolower(ch);
  635.         } while ((ch != 'a') && (ch != 'e') && (ch != 'r'));
  636.         if (ch == 'e') {
  637.             message("*** Save aborted - press any key to continue ***");
  638.             getch();
  639.             return;
  640.         }
  641.         if (ch == 'a') {
  642.             if ((tmp = fopen(fnx, "at")) == NULL) {
  643.                 message("*** Cannot open file for appending - "
  644.                     "press any key to continue ***");
  645.                 getch();
  646.             }
  647.         }
  648.         else {
  649.             if ((tmp = fopen(fnx, "wt")) == NULL) {
  650.                 message("*** Cannot open file for output - "
  651.                     "press any key to continue ***");
  652.                 getch();
  653.             }
  654.         }
  655.     }
  656.     else {
  657.         if ((tmp = fopen(fnx, "wt")) == NULL) {
  658.             message("*** Cannot open file for output - "
  659.                 "press any key to continue ***");
  660.             getch();
  661.         }
  662.     }
  663.  
  664.     if (tmp != NULL) {
  665.         message("*** Saving Article(s) - please wait ***");
  666.  
  667.         fn = make_news_group_name(gp->group);
  668.         if ((tmp_file = fopen(fn, "rb")) != NULL) {
  669.             while (fgets(buf, sizeof(buf), tmp_file) != NULL) {
  670.                 if (strncmp(buf, "@@@@END\n", 7) == 0)
  671.                     fputc(0x0c, tmp);
  672.                 else
  673.                     fputs(buf, tmp);
  674.             }
  675.             fclose(tmp_file);
  676.         }
  677.         fclose(tmp);
  678.         message("Article(s) saved - press any key to continue");
  679.         getch();
  680.     }
  681. }
  682.  
  683. /*------------------------ save a thread ------------------------------*/
  684. void            save_thread_to_disk(ACTIVE * gp, ARTICLE * this_thread)
  685. {
  686.     ART_ID         *id;
  687.     char           *fn;
  688.     int             a_ct;
  689.     char            fnx[80];
  690.     int             ch;
  691.     FILE           *tmp, *tmp_file;
  692.     time_t          now;
  693.     struct tm      *tmnow;
  694.     char            timestr[64];
  695.     char            buf[512], prompt[128];
  696.  
  697.     if (xfile == TRUE) {                  /* extract function */
  698.         strcpy(fnx, my_stuff.mail_dir);
  699.         strcat(fnx, my_stuff.extruser);
  700.         strcat(fnx, ".txt");
  701.     }
  702.     else {
  703.         sprintf(prompt, "Enter filename? [%s] ", save_name);
  704.         lmessage(prompt);
  705.         gets(fnx);
  706.         if (strlen(fnx) > 0)
  707.             strcpy(save_name, fnx);
  708.         else
  709.             strcpy(fnx, save_name);
  710.     }
  711.  
  712.     tmp = NULL;
  713.  
  714.     if (xfile == TRUE) {                  /* extract function */
  715.         if (access(fnx, 0) == 0) {
  716.             if ((tmp = fopen(fnx, "at")) == NULL) {
  717.                 message("*** Cannot open file for appending -"
  718.                         "press any key to continue ***");
  719.                 getch();
  720.             }
  721.         }
  722.         else {
  723.             if ((tmp = fopen(fnx, "wt")) == NULL) {
  724.                 message("*** Cannot open file for output - press a key to continue ***");
  725.                 getch();
  726.             }
  727.         }
  728.     }
  729.     else {                                  /* save function */
  730.         if (access(fnx, 0) == 0) {
  731.             message("File exists - append/replace/exit(a/r/e)? ");
  732.             do
  733.                 {
  734.                 ch = getch();
  735.                 ch = tolower(ch);
  736.             }
  737.             while ((ch != 'a') && (ch != 'e') && (ch != 'r'));
  738.             if (ch == 'e') {
  739.                 message("*** Save aborted - press any key to continue ***");
  740.                 getch();
  741.                 return;
  742.             }
  743.             if (ch == 'a') {
  744.                 if ((tmp = fopen(fnx, "at")) == NULL) {
  745.                     message("*** Cannot open file for appending - "
  746.                             "please any key to continue ***");
  747.                     getch();
  748.                 }
  749.             }
  750.             else {
  751.                 if ((tmp = fopen(fnx, "wt")) == NULL) {
  752.                     message("*** Cannot open file for output - press a key to continue ***");
  753.                     getch();
  754.                 }
  755.             }
  756.         }
  757.         else {
  758.             if ((tmp = fopen(fnx, "wt")) == NULL) {
  759.                 message("*** Cannot open file for output - press a key to continue ***");
  760.                 getch();
  761.             }
  762.         }
  763.     }
  764.  
  765.     if (tmp != NULL) {
  766.  
  767.         message("*** Saving Article(s) - please wait ***");
  768.  
  769.         fn = make_news_group_name(gp->group);
  770.  
  771.         id = this_thread->art_num;
  772.         a_ct = 0;
  773.  
  774.         while (id != NULL) {
  775.  
  776.             if (xfile == TRUE) {
  777.                 tzset();
  778.                 time(&now);
  779.                 tmnow = localtime(&now);
  780.                 strftime(timestr, sizeof(timestr), "%a %b %d %H:%M:%S %Z %Y", tmnow);
  781.                 fprintf(tmp, "From snews@%s.%s %s\n", my_stuff.my_site,
  782.                         my_stuff.my_domain, timestr);
  783.             }
  784.  
  785.             if ((tmp_file = fopen(fn, "rb")) != NULL) {
  786.  
  787.                 fseek(tmp_file, id->art_off, SEEK_SET);
  788.                 while (fgets(buf, sizeof(buf), tmp_file) != NULL) {
  789.                     if (strncmp(buf, "@@@@END\n", 7) == 0)
  790.                         break;
  791.                     fputs(buf, tmp);
  792.                 }
  793.                 fclose(tmp_file);
  794.  
  795.             }
  796.             
  797.             fputs("\n\n", tmp);
  798.  
  799.             a_ct++;
  800.             id = id->next_art;
  801.  
  802.         }
  803.  
  804.         fclose(tmp);
  805.         message("Article(s) saved - press any key to continue");
  806.     }
  807. }
  808.  
  809. /*------------------------ search through articles ------------------------*/
  810. int             search_message(char *fn, ART_ID *current, int forward)
  811. {
  812.     int            offset = 0, found = FALSE;
  813.     TEXT          *tx;
  814.     LINES         *text;
  815.     char           prompt[128], pattern[128];
  816.     int            irq = FALSE;
  817.  
  818.     sprintf(prompt, "Search %s for [%s] ",
  819.         forward ? "forward" : "backward", search_text);
  820.     lmessage(prompt);
  821.     gets(pattern);
  822.     if (pattern[0] == 0x1b)
  823.         return(0);
  824.  
  825.     if (strlen(pattern) > 0)
  826.         strcpy(search_text, pattern);
  827.     else
  828.         strcpy(pattern, search_text);
  829.  
  830.     strlwr(pattern);
  831.  
  832.     message("*** searching - please wait (press <ESC> to abort ***");
  833.  
  834.     for (;;) {
  835.  
  836.         current = forward ? current->next_art : current->prev_art;
  837.         if (current == NULL)
  838.             break;
  839.         offset++;
  840.         tx = load_article(fn, current->art_off, FALSE);
  841.  
  842.         for (text=tx->start; !found && text!=NULL; text=text->next) {
  843.             strlwr(text->data);
  844.             found = (strstr(text->data, pattern) != NULL);
  845.         }
  846.  
  847.         free_article(tx);
  848.  
  849.         if (kbhit())
  850.             irq = (getch() == 27);
  851.  
  852.         if (found || irq)
  853.             break;
  854.     }
  855.  
  856.     if (!found) {
  857.         sprintf(prompt, "*** '%s' not found - press any key ***", pattern);
  858.         message(prompt);
  859.         getch();
  860.     }
  861.  
  862.     return found ? offset : 0;
  863. }
  864.  
  865. /*------------------------ read a thread ------------------------------*/
  866. int             read_thread(ACTIVE * gp, ARTICLE * this_thread, ART_ID * first, int a_ct)
  867. {
  868.     ART_ID         *id;
  869.     char           *fn;
  870.     int             idx, res;
  871.     TEXT           *tx;
  872.     char            author[128], msg_id[128], r_name[128];
  873.     CROSS_POSTS    *h, *h0;
  874.     ACTIVE         *gx;
  875.     int             junk_flag;              /* nz - going to junk group */
  876.  
  877.     fn = make_news_group_name(gp->group);
  878.  
  879.     id = first;
  880.  
  881.     while (id != NULL) {
  882.         tx = load_article(fn, id->art_off, TRUE);
  883.         res = read_article(gp, tx, this_thread, a_ct, id);
  884.  
  885.         if (res & EX_NOTREAD)
  886.             res &= ~EX_NOTREAD;
  887.         else {
  888.             /* mark this article as read */
  889.             idx = (int) ((id->id) - gp->lo_num - 1);
  890.             *((gp->read_list) + idx) = (char) TRUE;
  891.  
  892.             /* mark the crossposts */
  893.             get_his_stuff(tx, author, msg_id, r_name);
  894.             if ((h0 = look_up_history(msg_id, gp->group)) != NULL) {
  895.                 h = h0;
  896.                 while (h != NULL) {
  897.                     gx = find_news_group(h->group, &junk_flag);
  898.                     idx = (int) ((h->art_num) - gx->lo_num - 1);
  899.                     *((gx->read_list) + idx) = (char) TRUE;
  900.                     h = h->next;
  901.                 }
  902.  
  903.                 free_cross_post_list(h0);
  904.                 h0 = NULL;
  905.             }
  906.         }
  907.  
  908.         DBGOUT(("%s:%d\n", __FILE__, __LINE__));
  909.         
  910.         free_article(tx);
  911.         tx = NULL;
  912.  
  913.         switch (res) {
  914.           case EX_QUIT:
  915.             id = NULL;
  916.  
  917.           case EX_NEXT_UNREAD:
  918.             while (id != NULL) {
  919.                 idx = (int) (id->id - gp->lo_num - 1);
  920.                 if (*((gp->read_list) + idx) == FALSE) {
  921.                     break;
  922.                 }
  923.                 a_ct++;
  924.                 id = id->next_art;
  925.             }
  926.  
  927.             if (id == NULL)
  928.                 message("-- No more articles in thread --");
  929.  
  930.             break;
  931.  
  932.           case EX_SEARCH_FORW:
  933.             res = search_message(fn, id, 1);
  934.             while (res--) {
  935.                 a_ct++;
  936.                 id = id->next_art;
  937.             }
  938.             break;
  939.  
  940.           case EX_SEARCH_BACKW:
  941.             res = search_message(fn, id, 0);
  942.             while (res--) {
  943.                 a_ct--;
  944.                 id = id->prev_art;
  945.             }
  946.             break;
  947.  
  948.           case EX_PREV:
  949.             if (id->prev_art) {
  950.                 id = id->prev_art;
  951.                 a_ct--;
  952.             }
  953.             break;
  954.  
  955.           case EX_PREVIOUS:
  956.             a_ct--;
  957.             id = id->prev_art;
  958.             break;
  959.  
  960.           case EX_FIRST:
  961.             while (id->prev_art != NULL) {
  962.                 id = id->prev_art;
  963.                 a_ct--;
  964.             }
  965.             break;
  966.  
  967.           case EX_LAST:
  968.             while (id->next_art != NULL) {
  969.                 id = id->next_art;
  970.                 a_ct++;
  971.             }
  972.             break;
  973.  
  974.           default:                          /* EX_NEXT */
  975.             a_ct++;
  976.             id = id->next_art;
  977.             break;
  978.         }                                  /* switch (res) */
  979.  
  980.     }
  981.  
  982.     return (res);
  983. }
  984.  
  985. /*--------------------- smarter subject compare --------------------------*/
  986. int                strip_off_part(char *str)
  987. {
  988.     char          *ptr = str + strlen(str);
  989.  
  990.     /* strip off (case-insensitive) things like:
  991.      *  - "Part01/10"
  992.      *  - "Part 01/10"
  993.      *  - "Part 01 of 10"
  994.      *  - "[1/10]"
  995.      *  - "(1 of 10)"
  996.      *  - "1 of 10"
  997.      *  - "Patch02a/04"
  998.      *  - "Patch20"
  999.      */
  1000.  
  1001.     while ( ptr > str && ptr[-1] == ' ' )
  1002.         ptr--;
  1003.  
  1004.     if ( ptr > str && (ptr[-1] == ')' || ptr[-1] == ']') )
  1005.         ptr--;
  1006.  
  1007.     while ( ptr > str && isdigit(ptr[-1]) )
  1008.         ptr--;
  1009.  
  1010.     if ( !isdigit(*ptr) )
  1011.         return 0;
  1012.  
  1013.     if ( ptr > str && ptr[-1] == '/' )
  1014.             ptr--;
  1015.         if ( ptr > str && ptr[-1] == '.' )
  1016.             ptr--;
  1017.     else if ( ptr > str + 3 && strnicmp(ptr - 4, " of ", 4) == 0 )
  1018.             ptr -= 4;
  1019.     else if ( ptr > str + 4 && strnicmp(ptr - 5, "Patch", 5) == 0 ) {
  1020.         ptr -= 5;
  1021.             goto label;
  1022.     }
  1023.      else if ( ptr > str + 5 && strnicmp(ptr - 6, "Patch ", 6) == 0 ) {
  1024.         ptr -= 6;
  1025.             goto label;
  1026.     }
  1027.       else
  1028.             return 0;
  1029.  
  1030.     if ( ptr > str && 'a' <= ptr[-1] && ptr[-1] <= 'z' )
  1031.             ptr--;
  1032.  
  1033.     while ( ptr > str && isdigit(ptr[-1]) )
  1034.             ptr--;
  1035.  
  1036.     if ( !isdigit(*ptr) )
  1037.             return 0;
  1038.  
  1039.     if ( ptr > str && (ptr[-1] == '(' || ptr[-1] == '[') )
  1040.             ptr--;
  1041.  
  1042.     while ( ptr > str && ptr[-1] == ' ' )
  1043.             ptr--;
  1044.  
  1045.     if ( ptr > str + 3 && strnicmp(ptr - 4, "Part", 4) == 0 )
  1046.             ptr -= 4;
  1047.  
  1048. label:    
  1049.     while ( ptr > str && ptr[-1] == ' ' )
  1050.             ptr--;
  1051.  
  1052.     if ( ptr > str && ptr[-1] == ',' )
  1053.             ptr--;
  1054.     else if ( ptr > str && ptr[-1] == ':' )
  1055.             ptr--;
  1056.  
  1057.     *ptr = 0;
  1058.     return 1;
  1059. }
  1060.  
  1061. /*------------------------------------------------------------------------*/
  1062. char              *skip_vi(char *str)
  1063. {
  1064.     char          *ptr = str;
  1065.  
  1066.     /* skip things like "v02i0027: " */
  1067.  
  1068.     while ( isspace(*ptr) )
  1069.             ptr++;
  1070.  
  1071.     if ( *ptr++ != 'v' )
  1072.             return str;
  1073.  
  1074.     if ( !isdigit(*ptr) )
  1075.             return str;
  1076.  
  1077.     while ( isdigit(*ptr) )
  1078.             ptr++;
  1079.  
  1080.     if ( *ptr++ != 'i' )
  1081.             return str;
  1082.  
  1083.     if ( !isdigit(*ptr) )
  1084.             return str;
  1085.  
  1086.     while ( isdigit(*ptr) )
  1087.             ptr++;
  1088.  
  1089.     if ( *ptr++ != ':' )
  1090.             return str;
  1091.  
  1092.     if ( *ptr++ != ' ' )
  1093.             return str;
  1094.  
  1095.     while ( isspace(*ptr) )
  1096.             ptr++;
  1097.  
  1098.     return ptr;
  1099. }
  1100.  
  1101. /*------------------------------------------------------------------------*/
  1102. int            smartcmp(char *str1, char *str2)
  1103. {
  1104.     int            slen;
  1105.     char           s1[256], s2[256];
  1106.  
  1107.     strcpy(s1, str1);
  1108.     strcpy(s2, str2);
  1109.     if (strip_off_part(s1) && strip_off_part(s2)) {
  1110.         str1 = skip_vi(s1);
  1111.         str2 = skip_vi(s2);
  1112.     }
  1113.  
  1114.     slen = (int) (strlen(str1) < strlen(str2))
  1115.           ? strlen(str1) : strlen(str2);
  1116.  
  1117.     if (my_stuff.match_len == -1)
  1118.         if (strlen(str1) <= strlen(str2))
  1119.             return -1;
  1120.         else
  1121.             return 1;
  1122.  
  1123.     if (my_stuff.match_len == 0)
  1124.         slen = 128;
  1125.  
  1126.     if (--slen < my_stuff.match_len)
  1127.         slen = my_stuff.match_len;
  1128.     return (strnicmp(str1, str2, slen));
  1129.  
  1130. }
  1131.  
  1132. /*------------------------- read the headers --------------------------*/
  1133. ARTICLE        *get_headers(ACTIVE * gp)
  1134. {
  1135.  
  1136.     /*
  1137.      * Read the files and get the headers
  1138.      */
  1139.  
  1140.     char           *fn;
  1141.     char            buf[512], fnx[256], *buf_p;
  1142.     long            g, n_read;
  1143.     FILE           *tmp_file;
  1144.  
  1145.     ARTICLE        *start, *that, *tmp;
  1146.     ARTICLE       **ptr;
  1147.     ART_ID         *art_this, *art_new;
  1148.     int             ct_art, cmp;
  1149.  
  1150.     n_read = 0;
  1151.     ct_art = 0;
  1152.     start = NULL;
  1153.  
  1154.     fn = make_news_group_name(gp->group);
  1155.     sprintf(fnx, "%s.IDX", fn);
  1156.  
  1157.     if ((tmp_file = fopen(fnx, "rb")) != NULL) {
  1158.  
  1159.         for (g = gp->lo_num + 1; g <= gp->hi_num; g++) {
  1160.  
  1161.             if ((n_read++ % 10) == 0) {
  1162.                 gotoxy(1, scr_rows);
  1163.                 printf("%d articles processed", n_read - 1);
  1164.             }
  1165.  
  1166.             /*
  1167.              * Read the index Search the linked list for the subject
  1168.              * - allocate a new subject if necessary
  1169.              * - add to the existing list
  1170.              */
  1171.  
  1172.             if (fgets(buf, 255, tmp_file) == NULL) {
  1173.                 gotoxy(1, scr_rows);
  1174.                 fprintf(stderr, "\nsnews: index file is corrupt (%s)\n", gp->group);
  1175.                 exit(1);
  1176.             }
  1177.             /* check all is in sync */
  1178.             if (g != atol(buf + 9)) {
  1179.                 gotoxy(1, scr_rows);
  1180.                 fprintf(stderr, "\nsnews: in %s article %ld found when %ld"
  1181.                         " expected\n", gp->group, atol(buf + 9), g);
  1182.                 exit(1);
  1183.             }
  1184.             /* skip the two eight digit numbers and the 9 and the spaces */
  1185.             buf_p = buf + 28;
  1186.  
  1187.             eat_gunk(buf_p);
  1188.  
  1189.             for (tmp=start; tmp != NULL; ) {
  1190.  
  1191.                 cmp = smartcmp(buf_p, tmp->header);
  1192.  
  1193.                 if (cmp > 0) {
  1194.                     if (tmp->left)
  1195.                         tmp = tmp->left;
  1196.                     else {
  1197.                         ptr = &(tmp->left);
  1198.                         tmp = NULL;
  1199.                         break;
  1200.                     }
  1201.                 }
  1202.                 else if (cmp < 0) {
  1203.                     if (tmp->right)
  1204.                         tmp = tmp->right;
  1205.                     else {
  1206.                         ptr = &(tmp->right);
  1207.                         tmp = NULL;
  1208.                         break;
  1209.                     }
  1210.                 }
  1211.                 else {
  1212.                     /* found this subject */
  1213.                     tmp->num_articles++;
  1214.  
  1215.                     /* allocate new article */
  1216.                     art_new = (ART_ID *) malloc(sizeof(ART_ID));
  1217.                     art_new->id = g;
  1218.                     art_new->art_off = atol(buf);
  1219.  
  1220.                     /* place it at end */
  1221.                     art_this = tmp->art_num;
  1222.                     while (art_this->next_art != NULL) {
  1223.                         art_this = art_this->next_art;
  1224.                     }
  1225.                     art_this->next_art = art_new;
  1226.  
  1227.                     art_new->prev_art = art_this;
  1228.                     art_new->next_art = NULL;
  1229.  
  1230.                     break;
  1231.                 }
  1232.             }
  1233.             
  1234.             if (tmp == NULL) {
  1235.                 /* not found - allocate new thread */
  1236.  
  1237.                 if (start == NULL) {
  1238.                     start = (ARTICLE *) malloc(sizeof(ARTICLE));
  1239.                     that = start;
  1240.                     that->last = NULL;
  1241.                 }
  1242.                 else {
  1243.                     ct_art++;
  1244.                     that->next = (ARTICLE *) malloc(sizeof(ARTICLE));
  1245.                     that->next->last = that;
  1246.                     that = that->next;
  1247.                     *ptr = that;
  1248.                 }
  1249.  
  1250.                 that->next = NULL;
  1251.                 that->index = ct_art;
  1252.                 that->left = that->right = NULL;
  1253.  
  1254.                 /* store article data */
  1255.                 strcpy(that->header, buf_p);
  1256.                 that->num_articles = 1;
  1257.                 that->art_num = (ART_ID *) malloc(sizeof(ART_ID));
  1258.                 that->art_num->prev_art = NULL;
  1259.                 that->art_num->next_art = NULL;
  1260.                 that->art_num->id = g;
  1261.                 that->art_num->art_off = atol(buf);
  1262.  
  1263.             }
  1264.          }
  1265.         fclose(tmp_file);
  1266.     }
  1267.     else {
  1268.         gotoxy(1, scr_rows);
  1269.         fprintf(stderr, "\nsnews: can't open index file %s\n", fnx);
  1270.         exit(1);
  1271.     }
  1272.  
  1273.     gp->threads = ct_art+1;
  1274.  
  1275.     return (start);
  1276.  
  1277. }
  1278.  
  1279.  
  1280.  
  1281. /*------------------------ clean up subject line ----------------------------*/
  1282. void            eat_gunk(char *buf)
  1283. {
  1284.  
  1285.     /*
  1286.      * This routine take the header line, and strips the word
  1287.      * header word, 'Re:', and any extra blanks, trim to 59
  1288.      * chars
  1289.      */
  1290.  
  1291.     char           *p, tmp[256];
  1292.  
  1293.     buf[58] = 0;
  1294.  
  1295.  
  1296.     /* strip the Subject: etc off the front */
  1297. /*    while ((strstr(buf, "Re:") != NULL) ||
  1298.            (strstr(buf, "re:") != NULL) ||
  1299.            (strstr(buf, "RE:") != NULL) || */
  1300.     while ((strnicmp(buf, "re:", 3) == 0) ||
  1301.            (strnicmp(buf, "From:", 5) == 0) ||
  1302.            (strnicmp(buf, "Path:", 5) == 0) ||
  1303.            (strnicmp(buf, "Subject:", 8) == 0) ||
  1304.            (strnicmp(buf, "Organization:", 13) == 0) ||
  1305.            (strnicmp(buf, "Organisation:", 13) == 0) ||
  1306.            (strnicmp(buf, "Followup-To:", 12) == 0)) {
  1307.         p = strstr(buf, ":");
  1308.         strcpy(buf, p + 1);
  1309.     }
  1310.  
  1311.     strcpy(tmp, buf);
  1312.     *buf = '\x00';
  1313.     p = strtok(tmp, " \t\n\r");
  1314.  
  1315.     while (p != NULL) {
  1316.  
  1317.         if (stricmp(p, "Re:") != 0) {
  1318.             strcat(buf, p);
  1319.             strcat(buf, " ");
  1320.         }
  1321.         else {
  1322.             if (strlen(buf) > 0) {
  1323.                 strcat(buf, p);
  1324.                 strcat(buf, " ");
  1325.             }
  1326.         }
  1327.         p = strtok(NULL, " \t\n\r");
  1328.     }
  1329.  
  1330.     if (strlen(buf) <= 1)
  1331.         strcpy(buf, "** no subject **");
  1332.     else
  1333.         buf[strlen(buf)-1] = '\0';        /* remove trailing space */
  1334. }
  1335.  
  1336.  
  1337. /*-------------------- release the subject structures ---------------------*/
  1338. void            free_header(ARTICLE * start)
  1339. {
  1340.  
  1341.     /*
  1342.      * Work our way through the subject structure releasing all the memory
  1343.      */
  1344.  
  1345.     ARTICLE        *a, *t;
  1346.     ART_ID         *u, *v;
  1347.  
  1348.     a = start;
  1349.  
  1350.     while (a != NULL) {
  1351.  
  1352.         u = a->art_num;
  1353.         while (u != NULL) {
  1354.             v = u->next_art;
  1355.             free(u);
  1356.             u = v;
  1357.         }
  1358.  
  1359.         t = a->next;
  1360.         free(a);
  1361.         a = t;
  1362.     }
  1363. }
  1364.  
  1365.  
  1366. /*------------------- count unread articles in a thread --------------------*/
  1367. int             count_unread_in_thread(ACTIVE * gp, ARTICLE * a)
  1368. {
  1369.  
  1370.     /*
  1371.      * Take the thread 'a' for the given group 'gp' and return the number
  1372.      * of unread articles
  1373.      */
  1374.  
  1375.     ART_ID         *id;
  1376.     int             ct, idx;
  1377.  
  1378.     ct = 0;
  1379.     id = a->art_num;
  1380.  
  1381.     while (id != NULL) {
  1382.         idx = (int) (id->id - gp->lo_num - 1);
  1383.         if (*((gp->read_list) + idx) == FALSE)
  1384.             ct++;
  1385.         id = id->next_art;
  1386.     }
  1387.  
  1388.     return (ct);
  1389. }
  1390.  
  1391.  
  1392. /*------------------- count unread articles in a group --------------------*/
  1393. int             count_unread_in_group(ACTIVE * gp)
  1394. {
  1395.  
  1396.     /*
  1397.      * Take the thread 'a' for the given group 'gp' and return the number
  1398.      * of unread articles
  1399.      */
  1400.  
  1401.     int             articles, ct, i;
  1402.  
  1403.     ct = 0;
  1404.     articles = (int) (gp->hi_num - gp->lo_num);
  1405.  
  1406.     for (i = 0; i < articles; i++) {
  1407.         if (*((gp->read_list) + i) == FALSE)
  1408.             ct++;
  1409.     }
  1410.  
  1411.     return (ct);
  1412. }
  1413.  
  1414.  
  1415.  
  1416. /*------------------- mark unread articles in a group --------------------*/
  1417. int             mark_group_as_read(ACTIVE * gp, int ask)
  1418. {
  1419.  
  1420.     /*
  1421.      * Take the given group 'gp' and mark all the unread articles
  1422.      * as read.
  1423.      */
  1424.  
  1425.     int             articles, i, ch;
  1426.  
  1427.     if ((ask == TRUE) && (my_stuff.expert != TRUE)) {
  1428.         message("Mark all articles in group as read (y/n) [y] ? ");
  1429.         do
  1430.             {
  1431.             ch = getch();
  1432.             ch = tolower(ch);
  1433.         }
  1434.         while ((ch != 'y') && (ch != 'n') && (ch != 'q') && (ch != ESCAPE) && (ch != '\r'));
  1435.     } else
  1436.         ch = 'y';
  1437.  
  1438.     if ((ch == 'y') || (ch == '\r')) {
  1439.  
  1440.         articles = (int) (gp->hi_num - gp->lo_num);
  1441.  
  1442.         for (i = 0; i < articles; i++)
  1443.             *((gp->read_list) + i) = (char) TRUE;
  1444.  
  1445.         return TRUE;
  1446.     }
  1447.     return FALSE;
  1448. }
  1449.  
  1450. /*----------------- mark read articles in a group as unread ----------------*/
  1451. void            mark_group_as_unread(ACTIVE * gp)
  1452. {
  1453.  
  1454.     /*
  1455.      * Take the given group 'gp' and mark all the read articles as
  1456.      * unread.
  1457.      */
  1458.  
  1459.     int             articles, i, ch;
  1460.  
  1461.     if (my_stuff.expert != TRUE) {
  1462.         message("Mark all articles in group as unread (y/n) [y] ? ");
  1463.         do
  1464.             {
  1465.             ch = getch();
  1466.             ch = tolower(ch);
  1467.         }
  1468.         while ((ch != 'y') && (ch != 'n') && (ch != 'q') && (ch != ESCAPE) && (ch != '\r'));
  1469.     }
  1470.     else
  1471.         ch = 'y';
  1472.  
  1473.     if ((ch == 'y') || (ch == '\r')) {
  1474.  
  1475.         articles = (int) (gp->hi_num - gp->lo_num);
  1476.  
  1477.         for (i = 0; i < articles; i++)
  1478.             *((gp->read_list) + i) = (char) FALSE;
  1479.  
  1480.     }
  1481. }
  1482.  
  1483. /*-------------------------- print a thread ---------------------------------*/
  1484. void        print_thread(ACTIVE *gp, ARTICLE *this_thread)
  1485. {
  1486.     ART_ID        *id;
  1487.     char        *fn;
  1488.     int        a_ct;
  1489.     FILE        *tmp_file;
  1490.     char        buf[512];
  1491.  
  1492.     message("*** printing Article(s) - please wait ***");
  1493.  
  1494.     fn = make_news_group_name(gp->group);
  1495.  
  1496.     id = this_thread->art_num;
  1497.     a_ct = 0;
  1498.  
  1499.     while (id != NULL) {
  1500.  
  1501.         if ((tmp_file = fopen(fn, "rb")) != NULL) {
  1502.     
  1503.             fseek(tmp_file, id->art_off, SEEK_SET);
  1504.             while (fgets(buf, sizeof(buf), tmp_file) != NULL) {
  1505.                 if (strncmp(buf, "@@@@END\n", 7) == 0)
  1506.                     break;
  1507.                 fputs(buf, stdprn);
  1508.                 fputc(0x0d, stdprn);
  1509.             }
  1510.             fputc(0x0c, stdprn);
  1511.             fflush(stdprn);
  1512.         }
  1513.  
  1514.         a_ct++;
  1515.         id = id->next_art;
  1516.     }
  1517. }
  1518.  
  1519.  
  1520. /*-------------------------- status message ---------------------------------*/
  1521. void            message(char *msg)
  1522. {
  1523.     int             x;
  1524.  
  1525.     gotoxy(1, scr_rows - 1);
  1526.     textbackground(msgb);
  1527.     textcolor(msgf);
  1528.     clreol();
  1529.  
  1530.     x = (scr_cols - strlen(msg)) / 2;
  1531.     gotoxy(x, scr_rows - 1);
  1532.     cprintf("%s", msg);
  1533.     textbackground(textb);
  1534.     textcolor(textf);
  1535. }
  1536.  
  1537.  
  1538. /*-------------------------- status message ---------------------------------*/
  1539. void            lmessage(char *msg)
  1540. {
  1541.     gotoxy(1, scr_rows - 1);
  1542.     textbackground(msgb);
  1543.     textcolor(msgf);
  1544.     clreol();
  1545.     cprintf("%s", msg);
  1546.     textbackground(textb);
  1547.     textcolor(textf);
  1548. }
  1549.  
  1550.  
  1551. /*-------------------------- status message ---------------------------------*/
  1552. void            command(char *msg)
  1553. {
  1554.     int             x;
  1555.  
  1556.     gotoxy(1, scr_rows);
  1557.     textbackground(msgb);
  1558.     textcolor(msgf);
  1559.     clreol();
  1560.  
  1561.     x = (scr_cols - strlen(msg)) / 2;
  1562.     gotoxy(x, scr_rows);
  1563.     cprintf(msg);
  1564.  
  1565.     textbackground(textb);
  1566.     textcolor(textf);
  1567. }
  1568.  
  1569. #ifndef ATARI
  1570. /****************************************************************************
  1571. *    system                                                                     *
  1572. *    Replacement for the standard system () function.                         *
  1573. *   Error code added MSM 3 Jun 93                                            *
  1574. *****************************************************************************/
  1575. int             system(const char *cmd)
  1576. {
  1577.     char           *p, *q;                  /* -> command and parameters */
  1578.     unsigned int    rc;                      /* return value */
  1579.     char            prompt[80];           /* Error message buffer */
  1580.  
  1581.     if (strlen(cmd) == 0) {
  1582.         rc = do_exec("", "", USE_ALL, 0xffff, NULL);
  1583.         return(rc);
  1584.     }
  1585.  
  1586.     p = strdup(cmd);
  1587.     if (p == NULL)
  1588.         return (0x102);                      /* no memory */
  1589.  
  1590.     q = p;
  1591.     while (*q != ' ' && *q != '\t' && q != '\0')
  1592.         q++;
  1593.  
  1594.     if (*q != '\0') {
  1595.         *q = '\0';
  1596.         q++;                              /* q -> parameters */
  1597.     }
  1598.     rc = do_exec(p, q, USE_ALL, 0xffff, NULL);
  1599.  
  1600.     if (rc > (unsigned) 0xff) {
  1601.         sprintf(prompt, "Unable to run command \"%s %s\"", p, q);
  1602.         message(prompt);
  1603.         getch();
  1604.     }
  1605.  
  1606. #if    defined (free)
  1607. #undef    free                              /* in case of debugging package! */
  1608. #endif
  1609.     free(p);
  1610.     return (rc);
  1611. }                                          /* int system (const char *cmd) */
  1612.  
  1613.  
  1614. #ifndef __TURBOC__
  1615.  
  1616. unsigned long farcoreleft(void)
  1617. {
  1618.  
  1619.     union _REGS inregs, outregs;
  1620.  
  1621.     _heapmin();
  1622.     inregs.h.ah = 0x48;
  1623.     inregs.x.bx = 0xffff;
  1624.     _intdos(&inregs, &outregs);
  1625.  
  1626.     return (16l * (long)outregs.x.bx);
  1627. }
  1628.  
  1629. unsigned long testcoreleft(void)
  1630. {
  1631.  
  1632.     union _REGS inregs, outregs;
  1633.  
  1634.     inregs.h.ah = 0x48;
  1635.     inregs.x.bx = 0xffff;
  1636.     _intdos(&inregs, &outregs);
  1637.  
  1638.     return (16l * (long)outregs.x.bx);
  1639. }
  1640.  
  1641. #endif    
  1642.  
  1643. #endif
  1644.